import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.ArrayList;

/**
 * This class is a Thread used to handle the socketQeue in 
 * the Webserver class, meaning that this will handle incoming
 * connections put in the socketQeue in Webserver.
 *
 * @author Marlon Etheredge
 * @author <a href="mailto:m.etheredge@gmail.com">m.etheredge@gmail.com</a>
 * @author <a href="http://www.foxage.nl/">http://www.foxage.nl</a>
 */
public class ConnectionHandler extends Thread {
    
    /** Webserver to wich this instance of ConnectionHandler belongs to. */
    private Webserver parentWebserver;

    /** Boolean used as run flag for the run() method. */
    private boolean running = true;

    /**
     * Constructs a new instance of Connectionhandler,
     * and starts this ConnectionHandler Thread.
     */
    public ConnectionHandler(Webserver parentWebserver) {
        this.parentWebserver = parentWebserver;

		if(Webster.verbose)
			Webster.log(Level.INFO, "New ConnectionHandler \"" + this + "\" belonging to \"" + parentWebserver + "\"");

		this.start();
    }

    /**
     * Run method for this ConnectionHandler Thread, 
     * will run as long as flag is true, will call Webserver.getNextSocket()
     * to get the next Socket in line, and handle this Socket,
     * while there is no next Socket in line, this Thread will sleep
     * in-case this Thread is no longer needed, the run flag will be set to false.
     */
    public void run() {
	    boolean isReading = true;
	    HttpMethod method;
	    HttpResponse httpResponse;
	    String line;
	    String[] methodParameters;
	    BufferedReader bufferedReader;
	    InputStreamReader inputStreamReader;
	    Socket socket;
	    OutputStream outputStream;
	    
	    while(this.running) {
			if(!this.running)
				break;

		    //Initialize or set variables to null
		    method = null;
		    line = null;
		    methodParameters = null;
		    bufferedReader = null;
		    inputStreamReader = null;
		    socket = null;
		    httpResponse = null;
		    outputStream = null;
		    
		    socket = this.parentWebserver.getNextSocket();

		    try {
			    inputStreamReader = new InputStreamReader(socket.getInputStream());
			    bufferedReader = new BufferedReader(inputStreamReader);
			    outputStream = socket.getOutputStream();
		    } catch(IOException exception) {
			    Webster.log(Level.SEVERE, "IOException thrown at ConnectionHandler.run(). " + exception);
		    }
		    
		    while(isReading) {

				if(!isReading)
					break;

			    try {
				    line = bufferedReader.readLine();
			    } catch(IOException exception) {
				    Webster.log(Level.SEVERE, "IOException thrown at ConnectionHandler.run(). " + exception);
			    }
			    
			    if(Webster.verbose)
					Webster.log(Level.INFO, "Received line: " + line);
			    
			    if(line == null) {
				    isReading = false;
			    } else {
				    if(method == null) {
					    if(line.startsWith("GET")) {
						    method = HttpMethod.GET;
						    methodParameters = line.substring(4).split(" ");
					    } else if(line.startsWith("POST")) {
						    method = HttpMethod.POST;
					    } else if(line.startsWith("HEAD")) {
						    method = HttpMethod.HEAD;
						    methodParameters = line.substring(4).split(" ");
					    } else if(line.startsWith("CONNECT")) {
						    method = HttpMethod.CONNECT;
					    } else if(line.startsWith("PUT")) {
						    method = HttpMethod.PUT;
					    } else if(line.startsWith("DELETE")) {
						    method = HttpMethod.DELETE;
					    } else if(line.startsWith("TRACE")) {
						    method = HttpMethod.TRACE;
					    } else if(line.startsWith("OPTIONS")) {
						    method = HttpMethod.OPTIONS;
					    } else {
						    Webster.log(Level.INFO, "Method was not yet set, though there was already " +
												    "a parameter call (" + line + "), could not properly " +
												    "handle this request.");
					    }
				    } else if(line.equals("")) {
						httpResponse = new HttpResponse(methodParameters, method);
					
						try {
							this.write(outputStream, httpResponse.build());
						} catch(IOException exception) {
							Webster.log(Level.SEVERE, "IOException thrown at ConnectionHandler.run()." + exception);
						}
						
						if(httpResponse.getCloseConnection()) {
							this.closeSocket(socket);
							isReading = false;
						}
				    }
			    }
		    }
		    
		    method = null;
		    line = null;
		    methodParameters = null;
		    bufferedReader = null;
		    inputStreamReader = null;
		    socket = null;
		    httpResponse = null;
		    outputStream = null;
		    isReading = true;
	    }
    }
    
    /**
     * Writes a byte[] to an OutputStream.
     *
     * @param outputStream Outputstream to write to
     * @param bytes Byte[] to write
     */
    public void write(OutputStream outputStream, byte[] bytes) throws IOException {
		outputStream.write(bytes);
	
		if(Webster.verbose)
			Webster.log(Level.INFO, "Sent back: \n" + new String(bytes));
    }
    
    /** Tells this ConnectionHandler to stop running. */
    public void stopRunning() {
		this.running = false;
    }
    
    /**  
     * Closes the Socket specified.
     *
     * @param socket Socket to close
     **/
    public void closeSocket(Socket socket) {
		try {	
			socket.close();
		} catch(IOException exception) {
			Webster.log(Level.SEVERE, "IOException thrown at ConnectionHandler.closeSocket(), " +
						  "therefore Socket (" + socket + ") could not be closed.");
		}
    }

}
